Go 中的一些坑

每种语言都有“坑”,有些是语言设计问题,有些是使用者的问题。不管如何,了解这些东西后能让我们的代码更健壮,BUG更少。

资源泄漏

time.Tick()

func Tick(d Duration) <-chan Time可用来做定时器,官网上的示例如下:

1
2
3
4
c := time.Tick(1 * time.Minute)
for now := range c {
fmt.Printf("%v %s\n", now, statusUpdate())
}

但使用此函数的话,没有办法释放底层的资源,看下此函数的说明:

Tick is a convenience wrapper for NewTicker providing access to the ticking channel only. While Tick is useful for clients that have no need to shut down the Ticker, be aware that without a way to shut it down the underlying Ticker cannot be recovered by the garbage collector; it “leaks”. Unlike NewTicker, Tick will return nil if d <= 0.

下面是验证的例子,会发现 tickLeak() 执行完后 CPU 依然占用很高:

1
2
3
4
5
6
7
8
9
10
func tickLeak() {
for i := 0; i < 1000; i++ {
time.Tick(time.Nanosecond)
}
}
func main() {
tickLeak()
fmt.Println("tickLeak finished")
time.Sleep(15 * time.Second)
}

所以应该像上面注释中说的那样,只在不需要关闭 Ticker 时使用 time.Tick()。否则使用 time.NewTicker(),并在不需要时主动调用 func (*Ticker) Stop

更新 map 中的值

map 中的值是结构体时,想要更新结构体的某一个字段:

1
2
3
4
5
6
7
8
9
10
11
12
package main

type T struct {
A int
B string
}

func main() {
m := make(map[string]T)
m["first"] = T{A: 10, B: "ok"}
m["first"].A = 1
}

这段代码编译会报错:

1
.\main.go:10: cannot assign to struct field m["first"].A in map

要这样写:

1
2
3
4
m["first"] = T{
A: 1,
B: m["first"].B,
}

或把 map 的值改成指针类型:

1
2
3
m := make(map[string]*T)
m["first"] = &T{A: 10, B: "ok"}
m["first"].A = 1

但要注意使用指针时,如果 map 中没有这个 key 的话会 panic:

1
panic: runtime error: invalid memory address or nil pointer dereference

make slice 时的参数

使用 make 创建 slice 时,可以指定 slice 的容量,减少 append 时重新分配内存的次数。但如果代码是这样写的,那就会引入BUG:

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
s := make([]int, 3)
s = append(s, 1, 2, 3)
fmt.Println(s)
}

上面代码会打印出:[0 0 0 1 2 3],因为 make 只指定了两个参数,这样 slice 的长度和容量都是 3,即 s 中已经有了三个值为 0 的元素。

rand.NewSource() 不是并发安全的

使用随机数时一般习惯用当前的时间戳做为种子写成这样 r := rand.New(rand.NewSource(time.Now().Unix())),之后再使用 r.Int() 等函数生成随机数,但这不是并发安全的,rand.NewSource() 的注释如下:

// NewSource returns a new pseudo-random Source seeded with the given value.
// Unlike the default Source used by top-level functions, this source is not
// safe for concurrent use by multiple goroutines.

如果直接使用 rand 包的随机数函数就没有问题,是因为 rand 包使用的全局变量定义成 var globalRand = New(&lockedSource{src: NewSource(1).(Source64)}),lockedSource 是有锁的:

1
2
3
4
type lockedSource struct {
lk sync.Mutex
src Source64
}